
package org.freez4.spring.dependency.lookup;

import org.freez4.spring.ioc.overview.domain.User;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;

import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * BeanFactory 类型安全依赖查找示例。
 * <p>
 * 总结: 推荐使用{@link ObjectProvider}来依赖查找,这个类既可以单一查找也可以集合类型查找。
 * <pre>
 * 单一类型查找:
 * 非安全
 *  1. BeanFactory#getBean、ObjectFactory#getObject
 * 安全
 *  2. ObjectProvider#getIfAvailable
 * 集合类型查找:
 * 安全
 *  1. ListableBeanFactory#getBeansOfType
 *  2. ObjectProvider#stream
 * </pre>
 *
 * @author 阿坤同学
 * @date 2023/12/8 10:33 AM
 */
public class TypeSafetyDependencyLookupDemo {

    private static final Logger logger = Logger.getLogger(TypeSafetyDependencyLookupDemo.class.getName());

    public static void main(String[] args) {
        // 创建 BeanFactory 容器
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
        // 将当前类 TypeSafetyDependencyLookupDemo 作为配置类（Configuration Class）
        applicationContext.register(TypeSafetyDependencyLookupDemo.class);
        // 启动应用上下文
        applicationContext.refresh();

        // 演示 BeanFactory#getBean 方法的安全性
        displayBeanFactoryGetBean(applicationContext);
        // 演示 ObjectFactory#getObject 方法的安全性
        displayObjectFactoryGetObject(applicationContext);
        // 演示 ObjectProvider#getIfAvaiable 方法的安全性
        displayObjectProviderIfAvailable(applicationContext);

        // 演示 ListableBeanFactory#getBeansOfType 方法的安全性
        displayListableBeanFactoryGetBeansOfType(applicationContext);
        // 演示 ObjectProvider Stream 操作的安全性
        displayObjectProviderStreamOps(applicationContext);

        // 关闭应用上下文
        applicationContext.close();
    }

    private static void displayObjectProviderStreamOps(AnnotationConfigApplicationContext applicationContext) {
        ObjectProvider<User> userObjectProvider = applicationContext.getBeanProvider(User.class);
        printBeansException("displayObjectProviderStreamOps", () -> userObjectProvider.forEach(user -> logger.log(Level.INFO, String.format("用户信息:%s", user.toString()))));
    }

    private static void displayListableBeanFactoryGetBeansOfType(ListableBeanFactory beanFactory) {
        printBeansException("displayListableBeanFactoryGetBeansOfType", () -> beanFactory.getBeansOfType(User.class));
    }

    private static void displayObjectProviderIfAvailable(AnnotationConfigApplicationContext applicationContext) {
        ObjectProvider<User> userObjectProvider = applicationContext.getBeanProvider(User.class);
        printBeansException("displayObjectProviderIfAvailable", userObjectProvider::getIfAvailable);
    }

    private static void displayObjectFactoryGetObject(AnnotationConfigApplicationContext applicationContext) {
        // ObjectProvider is ObjectFactory
        ObjectFactory<User> userObjectFactory = applicationContext.getBeanProvider(User.class);
        printBeansException("displayObjectFactoryGetObject", userObjectFactory::getObject);
    }

    public static void displayBeanFactoryGetBean(BeanFactory beanFactory) {
        printBeansException("displayBeanFactoryGetBean", () -> beanFactory.getBean(User.class));
    }

    private static void printBeansException(String source, Runnable runnable) {
        logger.log(Level.INFO, () -> "==========================================");
        logger.log(Level.INFO, () -> "Source from :" + source);
        try {
            runnable.run();
        } catch (BeansException exception) {
            logger.log(Level.INFO, exception::getMessage);
        }
    }

    @Bean
    public User user() {
        User user = new User();
        user.setId(1001);
        user.setName("free");
        user.setAge(100);
        return user;
    }
}
